﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

using Microsoft.Phone.Controls;

namespace Tequila.Phone.Controls
{
    /// <summary>
    /// Infinite <see cref="ListBox">ListBox</see>.
    /// </summary>
    [TemplatePart(Name = InfiniteListBoxTemplateConstants.ScrollableContainerName, Type = typeof(ScrollViewer))]
    [TemplatePart(Name = InfiniteListBoxTemplateConstants.ProgressIndicatorName, Type = typeof(PerformanceProgressBar))]
    public class InfiniteListBox : ListBox
    {
        /// <summary>
        /// Class-specific constants.
        /// </summary>
        protected static class InfiniteListBoxTemplateConstants
        {
            /// <summary>
            /// Name of the scrollable container.
            /// </summary>
            public const string ScrollableContainerName = "ScrollViewer";
            /// <summary>
            /// Name of the progress indicator.
            /// </summary>
            public const string ProgressIndicatorName = "loadingIndicator";
        }

        private double _previousOffsetDelta;

        #region Dependency Properties

        /// <summary>
        /// <see cref="DependencyProperty">Dependency Property</see>.
        /// </summary>
        public static DependencyProperty PreloadOffsetDeltaProperty =
            DependencyProperty.Register("PreloadOffsetDelta",
                                        typeof (double),
                                        typeof (InfiniteListBox),
                                        new PropertyMetadata(2.0, OnPreloadOffsetDeltaPropertyChanged));

        /// <summary>
        /// <see cref="DependencyProperty">Dependency Property</see>.
        /// </summary>
        public static DependencyProperty IsLoadingMoreItemsProperty =
            DependencyProperty.Register("IsLoadingMoreItems",
                                        typeof (bool),
                                        typeof (InfiniteListBox),
                                        new PropertyMetadata(OnIsLoadingPropertyChanged));

        /// <summary>
        /// <see cref="DependencyProperty">Dependency Property</see>.
        /// </summary>
        public static DependencyProperty ProgressForegroundProperty =
            DependencyProperty.Register("ProgressForeground",
                                        typeof (Brush),
                                        typeof (InfiniteListBox),
                                        null);

        private static readonly DependencyProperty VerticalOffsetProperty =
            DependencyProperty.Register("VerticalOffset",
                                        typeof (double),
                                        typeof (InfiniteListBox),
                                        new PropertyMetadata(OnVerticallOffsetPropertyChanged));

        #endregion

        /// <summary>
        /// Adds or removes more items loading <see cref="EventHandler{TEventArgs}">event</see>.
        /// </summary>
        public event EventHandler<EventArgs> LoadMoreItems;

        /// <summary>
        /// Initializes new instance of class.
        /// </summary>
        public InfiniteListBox()
        {
            DefaultStyleKey = typeof (InfiniteListBox);
        }

        #region Dependency Property Getters & Setters

        /// <summary>
        /// Gets or sets delta which when riched, triggers more items loading process.
        /// </summary>
        public double PreloadOffsetDelta
        {
            get { return (double) GetValue(PreloadOffsetDeltaProperty); }
            set { SetValue(PreloadOffsetDeltaProperty, value); }
        }

        /// <summary>
        /// Gets or sets flag that determines whether more items are being loaded or not.
        /// </summary>
        public bool IsLoadingMoreItems
        {
            get { return (bool) GetValue(IsLoadingMoreItemsProperty); }
            set { SetValue(IsLoadingMoreItemsProperty, value); }
        }

        /// <summary>
        /// Gets or sets progress bar foreground <see cref="Brush">brush</see>.
        /// </summary>
        public Brush ProgressForeground
        {
            get { return GetValue(ProgressForegroundProperty) as Brush; }
            set { SetValue(ProgressForegroundProperty, value); }
        }
        
        #endregion

        /// <summary>
        /// Builds the visual tree for the <see cref="T:System.Windows.Controls.ListBox"/> control when a new template is applied.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            ScrollViewer = GetTemplateChild(InfiniteListBoxTemplateConstants.ScrollableContainerName) as ScrollViewer;

            if (ScrollViewer != null)
            {
                ScrollViewer.Loaded += OnScrollViewerLoaded;
            }

            LoadingIndicator = GetTemplateChild(InfiniteListBoxTemplateConstants.ProgressIndicatorName) as PerformanceProgressBar;
        }

        /// <summary>
        /// Gets or sets <see cref="ScrollViewer">Scrollable container</see>.
        /// </summary>
        protected ScrollViewer ScrollViewer { get; set; }

        /// <summary>
        /// Gets or sets <see cref="PerformanceProgressBar">Loading progress indicator</see>.
        /// </summary>
        protected PerformanceProgressBar LoadingIndicator { get; set; }

        #region Dependency Properties Callbacks

        private static void OnPreloadOffsetDeltaPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctrl = d as InfiniteListBox;

            if (ctrl == null)
            {
                return;
            }

            ctrl.OnPreloadOffsetDeltaPropertyChanged(e);
        }

        private static void OnVerticallOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctrl = d as InfiniteListBox;

            if (ctrl == null)
            {
                return;
            }

            ctrl.OnVerticallOffsetPropertyChanged(e);
        }

        private static void OnIsLoadingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctrl = d as InfiniteListBox;

            if (ctrl == null)
            {
                return;
            }

            ctrl.OnIsLoadingPropertyChanged(e);
        }

        #endregion

        private void OnScrollViewerLoaded(object sender, RoutedEventArgs e)
        {
            var scrolViewer = sender as ScrollViewer;

            if (scrolViewer == null)
            {
                return;
            }
            var binding = new Binding
                              {
                                  Source = scrolViewer,
                                  Path = new PropertyPath("VerticalOffset"),
                                  Mode = BindingMode.OneWay
                              };

            SetBinding(VerticalOffsetProperty, binding);
        }

        /// <summary>
        /// Processes IsLoadingMoreItems property changing.
        /// </summary>
        /// <param name="e"><see cref="DependencyPropertyChangedEventArgs">Arguments</see>.</param>
        protected void OnIsLoadingPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if (LoadingIndicator == null)
            {
                return;
            }

            var val = (bool) e.NewValue;

            LoadingIndicator.IsIndeterminate = val;
            LoadingIndicator.Visibility = val ? Visibility.Visible : Visibility.Collapsed;
        }

        /// <summary>
        /// Processes PreloadOffsetDelta property changing.
        /// </summary>
        /// <param name="e"><see cref="DependencyPropertyChangedEventArgs">Arguments</see>.</param>
        protected virtual void OnPreloadOffsetDeltaPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            OnVerticallOffsetPropertyChanged(new DependencyPropertyChangedEventArgs());
        }

        /// <summary>
        /// Processes ScrollViewer vertical offset property changing.
        /// </summary>
        /// <param name="e"><see cref="DependencyPropertyChangedEventArgs">Arguments</see>.</param>
        protected virtual void OnVerticallOffsetPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if(ScrollViewer == null)
            {
                return;
            }

            var currentScrollableHeight = ScrollViewer.ScrollableHeight;
            var currentVerticalOffset = ScrollViewer.VerticalOffset;
            var currentOffsetDelta = currentScrollableHeight - currentVerticalOffset;
            var isDownScrolling = _previousOffsetDelta - currentOffsetDelta >= 0.0;

            _previousOffsetDelta = currentOffsetDelta;

            if (IsLoadingMoreItems || !isDownScrolling || (currentOffsetDelta > PreloadOffsetDelta))
            {
                return;
            }

            OnLoadMoreItems(EventArgs.Empty);
        }

        /// <summary>
        /// Triggers LoadMoreItems event.
        /// </summary>
        /// <param name="e"><see cref="EventArgs">Arguments</see>.</param>
        protected void OnLoadMoreItems(EventArgs e)
        {
            var handler = LoadMoreItems;

            if (handler != null)
            {
                handler(this, e);
            }
        }
    }
}